/*
 * Via82c686B south bridge support
 *
 * Copyright (c) 2008 yajin (yajin@vm-kernel.org)
 *
 * This code is licensed under the GNU GPL v2.
 */

#include "hw.h"
#include "pc.h"
#include "fdc.h"
#include "net.h"
#include "boards.h"
#include "smbus.h"
#include "block.h"
#include "flash.h"
#include "mips.h"
#include "pci.h"
#include "qemu-char.h"
#include "sysemu.h"
#include "audio/audio.h"
#include "boards.h"


typedef uint32_t pci_addr_t;
#include "pci_host.h"


#define MAX_IDE_BUS 2

typedef struct SuperIOConfig
{
    uint8_t config[0xff];
    uint8_t index;
    uint8_t data;
} SuperIOConfig;


PCIDevice *via82c686b_dev;
static SuperIOConfig *superio_conf;
static PITState *pit;


static void superio_ioport_writeb(void *opaque, uint32_t addr, uint32_t data)
{
    int can_write;

    printf("superio_ioport_writeb  address %x  val %x  \n\n", addr, data);

    if (addr == 0x3f0)
    {
        superio_conf->index = data & 0xff;
    }
    else
    {
        /*0x3f1 */
        switch (superio_conf->index)
        {
        case 0x00 ... 0xdf:
        case 0xe4:
        case 0xe5:
        case 0xe9 ... 0xed:
        case 0xf3:
        case 0xf5:
        case 0xf7:
        case 0xf9 ... 0xfb:
        case 0xfd ... 0xff:
            can_write = 0;
            break;
        default:
            can_write = 1;

            if (can_write)
            {
                switch (superio_conf->index)
                {
                case 0xe7:
                    if ((data & 0xff) != 0xfe)
                    {
                        printf("chage uart 1 base. unsupported yet \n");
                    }
                    break;
                case 0xe8:
                    if ((data & 0xff) != 0xbe)
                    {
                        printf("chage uart 2 base. unsupported yet \n");
                    }
                    break;

                default:
                    superio_conf->config[superio_conf->index] = data & 0xff;
                }
            }

        }

        superio_conf->config[superio_conf->index] = data & 0xff;
    }



}
static uint32_t superio_ioport_readb(void *opaque, uint32_t addr)
{
    printf("superio_ioport_readb  address %x   \n\n", addr);
    return (superio_conf->config[superio_conf->index]);
}

static void via82c686b_reset(PCIDevice * d)
{
    uint8_t *pci_conf = d->config;

    pci_conf[0x04] = 0x87;
    pci_conf[0x05] = 0x00;
    pci_conf[0x06] = 0x00;
    pci_conf[0x07] = 0x02;
    pci_conf[0x48] = 0x01;
    pci_conf[0x4a] = 0x04;
    pci_conf[0x4f] = 0x03;
    /*IDE Second Channel IRQ Routing -> IRQ 14 */
    /*IDE Primary Channel IRQ Routing -> IRQ 15 */
    pci_conf[0x4a] = 0x04;
    pci_conf[0x4F] = 0x03;
    pci_conf[0x50] = 0x2d;
    pci_conf[0x59] = 0x04;
    pci_conf[0x5a] = 0x04;
    pci_conf[0x5f] = 0x04;
    pci_conf[0x77] = 0x10;

    superio_conf->config[0xe0] = 0x3c;
    superio_conf->config[0xe2] = 0x03;
    superio_conf->config[0xe3] = 0xfc;
    superio_conf->config[0xe6] = 0xde;
    superio_conf->config[0xe7] = 0xfe;
    superio_conf->config[0xe8] = 0xbe;

}




/*read config pci function0 registers. PCI-ISA bridge*/
static uint32_t via82c686b_read_config(PCIDevice * d, uint32_t address, int len)
{
    uint32_t val;

    printf("via82c686b_read_config  address %x  len %x \n\n", address, len);

    switch (len)
    {
    default:
    case 4:
        if (address <= 0xfc)
        {
            val = le32_to_cpu(*(uint32_t *) (d->config + address));
            break;
        }
        /* fall through */
    case 2:
        if (address <= 0xfe)
        {
            val = le16_to_cpu(*(uint16_t *) (d->config + address));
            break;
        }
        /* fall through */
    case 1:
        val = d->config[address];
        break;
    }
    return val;

}

/*write config pci function0 registers. PCI-ISA bridge*/
static void via82c686b_write_config(PCIDevice * d, uint32_t address, uint32_t val, int len)
{
    int can_write, i;
    uint32_t end, addr;

    printf("via82c686b_write_config  address %x  val %x len %x \n\n", address, val, len);

    /* not efficient, but simple */
    addr = address;
    for (i = 0; i < len; i++)
    {
        /* default read/write accesses */
        switch (addr)
        {
        case 0x00 ... 0x03:
        case 0x08 ... 0x3f:

            can_write = 0;
            break;
        default:
            can_write = 1;
            break;
        }
        if (can_write)
        {
            switch (addr)
            {
            case 0x85:
                /*enable or disable super IO configure */
                if (val & 0x2)
                {
                    /*floppy also uses 0x3f0 and 0x3f1. But we do not emulate flopy,so just set it here. */
                    isa_unassign_ioport(0x3f0, 2);
                    register_ioport_read(0x3f0, 2, 1, superio_ioport_readb, d);
                    register_ioport_write(0x3f0, 2, 1, superio_ioport_writeb, d);
                }
                else
                {
                    isa_unassign_ioport(0x3f0, 2);
                }
                break;

            }

            d->config[addr] = val;
        }
        else
        {
            fprintf(stderr, "warnning. via82c686b_write_config :write to read only pci conf register addr %x\n", addr);
        }
        if (++addr > 0xff)
            break;
        val >>= 8;
    }





}
static void via82c686b_save(QEMUFile * f, void *opaque)
{
    PCIDevice *d = opaque;
    pci_device_save(d, f);
}

static int via82c686b_load(QEMUFile * f, void *opaque, int version_id)
{
    PCIDevice *d = opaque;
    if (version_id != 2)
        return -EINVAL;
    return pci_device_load(d, f);
}


/*init the PCI-to-ISA bridge */
int via82c686b_init(PCIBus * bus, int devfn, qemu_irq * pic)
{
    PCIDevice *d;
    uint8_t *pci_conf;
    int index;
    int i;
    RTCState *rtc_state;


    BlockDriverState *hd[MAX_IDE_BUS * MAX_IDE_DEVS];


    d = pci_register_device(bus, "via82c686b", sizeof(PCIDevice),
                            devfn, via82c686b_read_config, via82c686b_write_config);
    if (d == NULL)
        return (-1);
    register_savevm("via82c686b", 0, 2, via82c686b_save, via82c686b_load, d);

    via82c686b_dev = d;
    pci_conf = d->config;

    /*http://www.pcidatabase.com/ */
    pci_conf[0x00] = 0x06;      /*0x1106 VIA */
    pci_conf[0x01] = 0x11;
    pci_conf[0x02] = 0x86;      /*0x0686 PCI-to-ISA bridge */
    pci_conf[0x03] = 0x06;
    pci_conf[0x0a] = 0x01;      // class_sub = PCI_ISA
    pci_conf[0x0b] = 0x06;      // class_base = PCI_bridge
    pci_conf[0x0e] = 0x80;      // header_type = PCI_multifunction, generic

    /*set super io config */
    superio_conf = qemu_mallocz(sizeof(*superio_conf));
    if (superio_conf == NULL)
        return (-1);


    if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS)
    {
        fprintf(stderr, "qemu: too many IDE bus\n");
        return (-1);
    }
    for (i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++)
    {
        index = drive_get_index(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS);
        if (index != -1)
            hd[i] = drives_table[index].bdrv;
        else
            hd[i] = NULL;
    }


    /*init other devices */
    pit = pit_init(0x40, pic[0]);

    i8042_init(pic[1], pic[12], 0x60);
    rtc_state = rtc_init(0x70, pic[8]);
    if (serial_hds[0])
        serial_init(0x3f8, pic[4], serial_hds[0]);
    if (serial_hds[1])
        serial_init(0x2f8, pic[3], serial_hds[1]);
    if (parallel_hds[0])
        parallel_init(0x378, pic[7], parallel_hds[0]);



    via82c686b_reset(d);

    return d->devfn;

}
